]> dgit.raspbian.org Git - gnome-shell.git/commitdiff
tests: Port jsParse test to jasmine
authorFlorian Müllner <fmuellner@gnome.org>
Sun, 21 Jan 2024 18:13:14 +0000 (19:13 +0100)
committerMarge Bot <marge-bot@gnome.org>
Tue, 21 May 2024 13:42:28 +0000 (13:42 +0000)
Based on https://bugzilla.gnome.org/show_bug.cgi?id=783738
from Sam Spilsbury.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3164>

tests/meson.build
tests/unit/jsParse.js

index 5adfcffe8740fde18cd9bc2c3f679c422bec3e66..0b333ad101e7dcd237da620a2104cfed4a616bba 100644 (file)
@@ -23,6 +23,7 @@ unit_tests = [
     'highlighter',
     'injectionManager',
     'insertSorted',
+    'jsParse',
 ]
 
 foreach test : unit_tests
@@ -41,7 +42,6 @@ foreach test : unit_tests
 endforeach
 
 legacy_tests = [
-    'jsParse',
     'markup',
     'params',
     'signalTracker',
index 37e2e0cb93ea11a281ea680baac8187b2054cac7..6191c02cc0f98ca195bbfe624ab9d90ab56c732c 100644 (file)
 /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
 
-// Test cases for MessageTray URLification
-
-const JsUnit = imports.jsUnit;
-
 import 'resource:///org/gnome/shell/ui/environment.js';
 
-import * as Assertions from '../common/assertions.js';
-
 import * as JsParse from 'resource:///org/gnome/shell/misc/jsParse.js';
 
-const HARNESS_COMMAND_HEADER = 'let imports = obj;' +
-                               'let global = obj;' +
-                               'let Main = obj;' +
-                               'let foo = obj;' +
-                               'let r = obj;';
-
-const testsFindMatchingQuote = [
-    {
-        input: '"double quotes"',
-        output: 0,
-    },
-    {
-        input: '\'single quotes\'',
-        output: 0,
-    },
-    {
-        input: 'some unquoted "some quoted"',
-        output: 14,
-    },
-    {
-        input: '"mixed \' quotes\'"',
-        output: 0,
-    },
-    {
-        input: '"escaped \\" quote"',
-        output: 0,
-    },
-];
-const testsFindMatchingSlash = [
-    {
-        input: '/slash/',
-        output: 0,
-    },
-    {
-        input: '/slash " with $ funny ^\' stuff/',
-        output: 0,
-    },
-    {
-        input: 'some unslashed /some slashed/',
-        output: 15,
-    },
-    {
-        input: '/escaped \\/ slash/',
-        output: 0,
-    },
-];
-const testsFindMatchingBrace = [
-    {
-        input: '[square brace]',
-        output: 0,
-    },
-    {
-        input: '(round brace)',
-        output: 0,
-    },
-    {
-        input: '([()][nesting!])',
-        output: 0,
-    },
-    {
-        input: '[we have "quoted [" braces]',
-        output: 0,
-    },
-    {
-        input: '[we have /regex [/ braces]',
-        output: 0,
-    },
-    {
-        input: '([[])[] mismatched braces ]',
-        output: 1,
-    },
-];
-const testsGetExpressionOffset = [
-    {
-        input: 'abc.123',
-        output: 0,
-    },
-    {
-        input: 'foo().bar',
-        output: 0,
-    },
-    {
-        input: 'foo(bar',
-        output: 4,
-    },
-    {
-        input: 'foo[abc.match(/"/)]',
-        output: 0,
-    },
-];
-const testsGetDeclaredConstants = [
-    {
-        input: 'const foo = X; const bar = Y;',
-        output: ['foo', 'bar'],
-    },
-    {
-        input: 'const foo=X; const bar=Y',
-        output: ['foo', 'bar'],
-    },
-];
-const testsIsUnsafeExpression = [
-    {
-        input: 'foo.bar',
-        output: false,
-    },
-    {
-        input: 'foo[\'bar\']',
-        output: false,
-    },
-    {
-        input: 'foo["a=b=c".match(/=/)',
-        output: false,
-    },
-    {
-        input: 'foo[1==2]',
-        output: false,
-    },
-    {
-        input: '(x=4)',
-        output: true,
-    },
-    {
-        input: '(x = 4)',
-        output: true,
-    },
-    {
-        input: '(x;y)',
-        output: true,
-    },
-];
-const testsModifyScope = [
-    "foo['a",
-    "foo()['b'",
-    "obj.foo()('a', 1, 2, 'b')().",
-    'foo.[.',
-    'foo]]]()))].',
-    "123'ab\"",
-    'Main.foo.bar = 3; bar.',
-    '(Main.foo = 3).',
-    'Main[Main.foo+=-1].',
-];
-
 //
 // Test javascript parsing
 //
+//
+// TODO: We probably want to change all these to use
+//       a table driven method, using for() inside
+//       a test body hampers readibility and
+//       debuggability when something goes wrong.
+//
+// NOTE: The inconsistent use of "" and '' quotes in this
+//       file is largely to handle nesting without passing
+//       escape markers to the functions under test. The
+//       preferred style in the shell is single quotes
+//       so we use that wherever possible.
 
-for (let i = 0; i < testsFindMatchingQuote.length; i++) {
-    let text = testsFindMatchingQuote[i].input;
-    let match = JsParse.findMatchingQuote(text, text.length - 1);
+describe('Matching quote search', () => {
+    const FindMatchingQuoteParameters = [
+        {
+            name: 'only double quotes',
+            input: "'double quotes'",
+            output: 0,
+        },
+        {
+            name: 'only single quotes',
+            input: '\'single quotes\'',
+            output: 0,
+        },
+        {
+            name: 'some parts unquoted and other parts quoted',
+            input: "some unquoted 'some quoted'",
+            output: 14,
+        },
+        {
+            name: 'mixed quotes',
+            input: "'mixed \" quotes\"'",
+            output: 0,
+        },
+        {
+            name: 'escaped quotes',
+            input: "'escaped \\' quote'",
+            output: 0,
+        },
+    ];
 
-    JsUnit.assertEquals(`Test testsFindMatchingQuote ${i}`,
-        match, testsFindMatchingQuote[i].output);
-}
+    for (const {name, input, output} of FindMatchingQuoteParameters) {
+        it(`finds a matching quote where there are ${name}`, () => {
+            const match = JsParse.findMatchingQuote(input, input.length - 1);
+            expect(match).toEqual(output);
+        });
+    }
+});
 
-for (let i = 0; i < testsFindMatchingSlash.length; i++) {
-    let text = testsFindMatchingSlash[i].input;
-    let match = JsParse.findMatchingSlash(text, text.length - 1);
+describe('Matching slash search', () => {
+    const FindMatchingSlashParameters = [
+        {
+            name: 'matching slashes',
+            input: '/slash/',
+            output: 0,
+        },
+        {
+            name: 'matching slashes with extraneous characters in-between',
+            input: '/slash " with $ funny ^\' stuff/',
+            output: 0,
+        },
+        {
+            name: 'mathcing slashes with some parts unslashed',
+            input: 'some unslashed /some slashed/',
+            output: 15,
+        },
+        {
+            name: 'matching slashes with an escaped slash in the middle',
+            input: '/escaped \\/ slash/',
+            output: 0,
+        },
+    ];
 
-    JsUnit.assertEquals(`Test testsFindMatchingSlash ${i}`,
-        match, testsFindMatchingSlash[i].output);
-}
+    for (let {name, input, output} of FindMatchingSlashParameters) {
+        it(`finds a matching slash where there are ${name}`, () => {
+            let match = JsParse.findMatchingQuote(input, input.length - 1);
+            expect(match).toEqual(output);
+        });
+    }
+});
 
-for (let i = 0; i < testsFindMatchingBrace.length; i++) {
-    let text = testsFindMatchingBrace[i].input;
-    let match = JsParse.findMatchingBrace(text, text.length - 1);
+describe('Matching brace search', () => {
+    const FindMatchingBraceParameters = [
+        {
+            name: 'square braces',
+            input: '[square brace]',
+            output: 0,
+        },
+        {
+            name: 'round braces',
+            input: '(round brace)',
+            output: 0,
+        },
+        {
+            name: 'braces with nesting',
+            input: '([()][nesting!])',
+            output: 0,
+        },
+        {
+            name: 'braces with quoted braces in the middle',
+            input: "[we have 'quoted [' braces]",
+            output: 0,
+        },
+        {
+            name: 'braces with regexed braces in the middle',
+            input: '[we have /regex [/ braces]',
+            output: 0,
+        },
+        {
+            name: 'mismatched braces',
+            input: '([[])[] mismatched braces ]',
+            output: 1,
+        },
+    ];
 
-    JsUnit.assertEquals(`Test testsFindMatchingBrace ${i}`,
-        match, testsFindMatchingBrace[i].output);
-}
+    for (let {name, input, output} of FindMatchingBraceParameters) {
+        it(`finds matching braces where there are ${name}`, () => {
+            let match = JsParse.findMatchingBrace(input, input.length - 1);
+            expect(match).toEqual(output);
+        });
+    }
+});
 
-for (let i = 0; i < testsGetExpressionOffset.length; i++) {
-    let text = testsGetExpressionOffset[i].input;
-    let match = JsParse.getExpressionOffset(text, text.length - 1);
+describe('Beginning of expression search', () => {
+    const ExpressionOffsetParameters = [
+        {
+            name: 'object property name',
+            input: 'abc.123',
+            output: 0,
+        },
+        {
+            name: 'function call result property name',
+            input: 'foo().bar',
+            output: 0,
+        },
+        {
+            name: 'centre of malformed function call expression',
+            input: 'foo(bar',
+            output: 4,
+        },
+        {
+            name: 'complete nested expression',
+            input: 'foo[abc.match(/"/)]',
+            output: 0,
+        },
+    ];
 
-    JsUnit.assertEquals(`Test testsGetExpressionOffset ${i}`,
-        match, testsGetExpressionOffset[i].output);
-}
+    for (const {name, input, output} of ExpressionOffsetParameters) {
+        it(`finds the beginning of a ${name}`, () => {
+            const match = JsParse.getExpressionOffset(input, input.length - 1);
+            expect(match).toEqual(output);
+        });
+    }
+});
 
-for (let i = 0; i < testsGetDeclaredConstants.length; i++) {
-    let text = testsGetDeclaredConstants[i].input;
-    let match = JsParse.getDeclaredConstants(text);
+describe('Constant variable search', () => {
+    const DeclaredConstantsParameters = [
+        {
+            name: 'two constants on one line with space between equals',
+            input: 'const foo = X; const bar = Y',
+            output: ['foo', 'bar'],
+        },
+        {
+            name: 'two constants on one line with no space between equlas',
+            input: 'const foo=X; const bar=Y;',
+            output: ['foo', 'bar'],
+        },
+    ];
 
-    Assertions.assertArrayEquals(`Test testsGetDeclaredConstants ${i}`,
-        match, testsGetDeclaredConstants[i].output);
-}
+    for (const {name, input, output} of DeclaredConstantsParameters) {
+        it(`finds ${name}`, () => {
+            const match = JsParse.getDeclaredConstants(input);
+            expect(match).toEqual(output);
+        });
+    }
+});
 
-for (let i = 0; i < testsIsUnsafeExpression.length; i++) {
-    let text = testsIsUnsafeExpression[i].input;
-    let unsafe = JsParse.isUnsafeExpression(text);
+describe('Expression safety determination', () => {
+    const UnsafeExpressionParams = [
+        {
+            name: 'property access',
+            input: 'foo.bar',
+            output: false,
+        },
+        {
+            name: 'property access by array',
+            input: 'foo[\'bar\']',
+            output: false,
+        },
+        {
+            name: 'expression with syntax error',
+            input: 'foo["a=b=c".match(/=/)',
+            output: false,
+        },
+        {
+            name: 'property access by array with nested const expression',
+            input: 'foo[1==2]',
+            output: false,
+        },
+        {
+            name: 'bracketed assignment no whitespace',
+            input: '(x=4)',
+            output: true,
+        },
+        {
+            name: 'bracked assignment with whitespace',
+            input: '(x = 4)',
+            output: true,
+        },
+        {
+            name: 'bracketed implicit call',
+            input: '(x;y)',
+            output: true,
+        },
+    ];
 
-    JsUnit.assertEquals(`Test testsIsUnsafeExpression ${i}`,
-        unsafe, testsIsUnsafeExpression[i].output);
-}
+    for (const {name, input, output} of UnsafeExpressionParams) {
+        const isOrIsNot = output ? 'is' : 'is not';
+        it(`finds that an expression which is a ${name} ${isOrIsNot} safe`, () => {
+            let unsafe = JsParse.isUnsafeExpression(input);
+            expect(unsafe).toEqual(output);
+        });
+    }
+});
 
 //
 // Test safety of eval to get completions
 //
+describe('Expression evaluation', () => {
+    const HARNESS_COMMAND_HEADER =
+        'let imports = obj;' +
+        'let global = obj;' +
+        'let Main = obj;' +
+        'let foo = obj;' +
+        'let r = obj;';
+
+    const ExpressionParameters = [
+        "foo['a",
+        "foo()['b'",
+        "obj.foo()('a', 1, 2, 'b')().",
+        'foo.[.',
+        'foo]]]()))].',
+        "123'ab\"",
+        'Main.foo.bar = 3; bar.',
+        '(Main.foo = 3).',
+        'Main[Main.foo+=-1].',
+    ];
+
+    function evalIfSafe(text) {
+        // Just as in JsParse.getCompletions, we will find the offset
+        // of the expression, test whether it is unsafe, and then eval it.
+        const offset = JsParse.getExpressionOffset(text, text.length - 1);
+        if (offset < 0)
+            return;
+
+        const matches = text.slice(offset).match(/(.*)\.(.*)/);
+        if (matches == null)
+            return;
 
-for (let i = 0; i < testsModifyScope.length; i++) {
-    let text = testsModifyScope[i];
-
-    const globalPropsPre = Object.getOwnPropertyNames(globalThis).sort();
-
-    // Just as in JsParse.getCompletions, we will find the offset
-    // of the expression, test whether it is unsafe, and then eval it.
-    let offset = JsParse.getExpressionOffset(text, text.length - 1);
-    if (offset >= 0) {
-        text = text.slice(offset);
-
-        let matches = text.match(/(.*)\.(.*)/);
-        if (matches) {
-            let [, base] = matches;
-
-            if (!JsParse.isUnsafeExpression(base)) {
-                try {
-                    eval(HARNESS_COMMAND_HEADER + base);
-                } catch (e) {
-                    JsUnit.assertNotEquals(`Code '${base}' is valid code`, e.constructor, SyntaxError);
-                }
-            }
-        }
+        const [, base] = matches;
+        if (JsParse.isUnsafeExpression(base))
+            return;
+
+        eval(HARNESS_COMMAND_HEADER + base);
     }
 
-    const globalPropsPost = Object.getOwnPropertyNames(globalThis).sort();
-    Assertions.assertArrayEquals('The global object was not modified',
-        globalPropsPre, globalPropsPost);
-}
+    for (const expression of ExpressionParameters) {
+        const globalPropsPre = Object.getOwnPropertyNames(globalThis).sort();
+
+        it(`of ${expression} does not throw syntax errors with a known safe expression`, () => {
+            expect(() => evalIfSafe(expression)).not.toThrow(SyntaxError);
+        });
+
+        const globalPropsPost = Object.getOwnPropertyNames(globalThis).sort();
+        it(`of ${expression} does not modify the global scope`, () => {
+            expect(globalPropsPre).toEqual(globalPropsPost);
+        });
+    }
+});